package org.dfl.core.data.types;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * a map type, where keys are Strings and values can be any type
 * @author Piotr Wlodarczyk
 *
 */
public class MapType extends Type {
	private Map<String,SubMapTypeContainer> keyMaps = new HashMap<String, SubMapTypeContainer>();
	private boolean fixed;
	
	public MapType() {
		super();
	}

	@Override
	public boolean canMerge(Type t) {
		if(t == this) return true;
		if(t instanceof MapType){
			MapType t1 = this;
			MapType t2 = (MapType) t;
			
			//najpierw sprawdzenie, czy klucze sa ok
			if(t1.isFixed() && t2.isFixed() && t1.keyMaps.size()!=t2.keyMaps.size()){
				return false;
			}
			
			Set<String> keys = new HashSet<String>();
			keys.addAll(t1.keyMaps.keySet());
			keys.addAll(t2.keyMaps.keySet());
			// jesli ok, to mergujemy (ze sprawdzeniem podtypow)
			for(String key : keys){
				if(t1.keyMaps.containsKey(key) && t2.keyMaps.containsKey(key)){
					if(!t1.keyMaps.get(key).getType().canMerge(t2.keyMaps.get(key).getType())){
						return false;
					}
				}else if(!(t2.keyMaps.containsKey(key) && !t1.isFixed())
						&&
						!(t1.keyMaps.containsKey(key) && !t2.isFixed())){
					return false;
				}
			}
			return true;
		}else if(t instanceof AnyType){
			return true;
		}else{
			return false;
		}
	}

	@Override
	protected boolean complain(Type t) {
		if(t == this) return true;
		if(t instanceof MapType){
			MapType t1 = this;
			MapType t2 = (MapType) t;
					
			Set<String> keys = new HashSet<String>();
			keys.addAll(t1.keyMaps.keySet());
			keys.addAll(t2.keyMaps.keySet());
			// jesli ok, to mergujemy (ze sprawdzeniem podtypow)
			for(String key : keys){
				if(t1.keyMaps.containsKey(key) && t2.keyMaps.containsKey(key)){
					Type ty1=t1.keyMaps.get(key).getType();
					Type ty2=t2.keyMaps.get(key).getType();
					
					if(!ty1.complain(ty2)){
						return false;
					}
				}else if(t2.keyMaps.containsKey(key) && !t1.isFixed()){
					addSubkey(key, t2.keyMaps.get(key).getType());
				}else if(t1.keyMaps.containsKey(key) && t2.isFixed()){
					return false;
				}
			}
			//jak zmergowalismy wszystkie klucze, to mergujemy calosc 
			t1.mergeParentsWith(t2);
			return true;
		}else if(t instanceof AnyType){
			return t.complain(this);
		}else{
			return false;
		}
	}

	@Override
	public Type getCopy() {
		MapType newMapType = new MapType();
		Set<String> keys = keyMaps.keySet();
		for(String key : keys){
			SubMapTypeContainer sub = keyMaps.get(key);
			if(!sub.isRemoveKeyWhenAny()){
				SubMapTypeContainer n = sub.copy();
				n.setRemoveKeyWhenAny(false);
				newMapType.addSubkey(key,n);
			}
		}
		return newMapType;
	}

	@Override
	public String save() {
		StringBuffer ret = new StringBuffer();
		ret.append("<");

		Set<String> keys = keyMaps.keySet();
		String delim = "";
		for(String key : keys){
			ret.append(delim);
			ret.append(key);
			ret.append(":");
			ret.append(keyMaps.get(key).getType().save());
			delim = ",";
		}
		ret.append(">");
		return ret.toString();
	}

	public void addSubkey(String key, Type type) {
		if(!isFixed()) keyMaps.put(key, new SubMapTypeContainer(key,type, this, false));
	}
	
	public void addSubkey(String key, SubMapTypeContainer value) {
		if(!isFixed()) keyMaps.put(key, value);
	}
	public void removeSubkey(String key) {
		if(!isFixed()) keyMaps.remove(key);
	}

	/**
	 * czy dana mapa jest stalego typu, czyli mozna jej dodawac/usuwac podtypy (fixed=nie mozna)
	 * @return
	 */
	public boolean isFixed(){
		return fixed;
	}
	
	public void setFixed(boolean fixed) {
		this.fixed = fixed;
	}

	public TypeContainer getSubtype(String key) {
		return keyMaps.get(key);
	}
	
	public void separateSubkeys(){
		// separuje wszystkie podklucze
		Set<String> keys = new HashSet<String>(keyMaps.keySet());
		for(String key : keys){
			keyMaps.get(key).separate();
		}
	}
	
	public Set<String> getSubkeysNames(){
		return keyMaps.keySet();
	}
}
